#pragma once

#include "cmd.hpp"
#include "classpath/class_path.hpp"
#include "instructions/instruction_factory.h"
#include "rtda/thread.h"
#include "rtda/heap/class_loader.h"

#include "instructions/base/class_init_logic.hpp"
#include "interpreter.h"
#include "rtda/heap/string_pool.h"

#include "native/java/lang/Class.hpp"
#include "native/java/lang/Object.hpp"
#include "native/java/lang/Float.hpp"
#include "native/java/lang/System.hpp"
#include "native/java/lang/String.hpp"
#include "native/java/lang/Double.hpp"
#include "native/java/lang/Throwable.hpp"
#include "native/sun/misc/VM.h"

namespace jvm {
	class Jvm
	{
	private:
		//Jvm(std::shared_ptr<Cmd> pcmd, std::shared_ptr<heap::ClassLoader> ploader, std::shared_ptr<rtda::Thread> pthread)
		Jvm(Cmd& cmd, std::shared_ptr<heap::ClassLoader> ploader, std::shared_ptr<rtda::Thread> pthread)
			:_cmd(cmd), _p_loader(ploader), _p_main_thread(pthread) {}

		Jvm(const Jvm&) = delete;
		Jvm& operator=(const Jvm&) = delete;

		Jvm(Jvm&&)noexcept = default;
		Jvm& operator=(Jvm&&)noexcept = default;

	public:
		//static std::shared_ptr<Jvm> newJvm(std::shared_ptr<Cmd> pcmd)
		static std::shared_ptr<Jvm> newJvm(Cmd& cmd)
		{
			std::shared_ptr<classpath::ClassPath> p_cp = std::make_shared<classpath::ClassPath>(cmd.get_jre_path(), cmd.get_class_path());
			auto ploader = heap::ClassLoader::newClassLoader(p_cp, cmd.is_verbose_class());
			return std::shared_ptr<Jvm>(new Jvm(cmd, ploader, std::make_shared<rtda::Thread>()));
		}

		void start()
		{
			lang::ClassNative::init();
			lang::ObjectNative::init();
			lang::FloatNative::init();
			lang::DoubleNative::init();
			lang::SystemNative::init();
			lang::StringNative::init();
			lang::ThrowableNative::init();
			misc::VMNative::init();

			initVM();
			execMain();
		}

		void initVM()
		{
			auto&& vmClass = _p_loader->loadClass("sun/misc/VM");
			instructions::initClass(_p_main_thread, vmClass);
			Interpreter::interpret(_p_main_thread, _cmd.is_verbose_inst());
		}

		void execMain()
		{
			std::string classname = _cmd.get_java_class();
			for (auto& e : classname) if ('.' == e) e = classpath::DIR_SEPARATOR;

			std::shared_ptr<heap::Class> main_class = _p_loader->loadClass(classname);
			std::shared_ptr<heap::Method> main_method = main_class->getMainMethod();
			if (!main_method)
			{
				LOG_ERROR("main method not found in class" << classname);
				return;
			}

			auto args_arr = createArgsArray();
			std::shared_ptr<rtda::Frame> pframe = _p_main_thread->newFrame(main_method);
			pframe->getLocalVars().setRef(0, args_arr);
			_p_main_thread->pushFrame(pframe);
			Interpreter::interpret(_p_main_thread, _cmd.is_verbose_inst());
		}

		heap::ObjectPtr createArgsArray()
		{
			auto string_class = _p_loader->loadClass("java/lang/String");
			const auto& args = _cmd.get_args();
			auto args_len = args.size();
			auto args_arr = string_class->getArrayClass()->newArray(args_len);
			auto&& j_args = args_arr->refs();
			for (int i = 0; i < args.size(); ++i)
			{
				j_args[i] = heap::StringPool::getInstance().JString(_p_loader, args[i]);
			}
			return args_arr;
		}

	private:
		//std::shared_ptr<Cmd> _p_cmd;
		Cmd& _cmd;
		std::shared_ptr<heap::ClassLoader> _p_loader;
		std::shared_ptr<rtda::Thread> _p_main_thread;
	};
}// namespace jvm

